﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms.VisualStyles;
using OptixDotNet;
using OptixDotNet.Math;
using RayDen.Library.Components;
using RayDen.Library.Components.Surface;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core.Collections;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;
using RayDen.Optix.Engine.Components;
using RayDen.Optix.Engine.Scene.Builders;
using Buffer = OptixDotNet.Buffer;
using DeviceBuffer = OptixDotNet.Buffer;

namespace RayDen.Optix.Engine.Scene.Factories
{
    public class OptixNodeFactory : OptixComponent
    {
        public OptixNodeFactory(Context ctx)
            : base(ctx)
        {
        }

        public Context Context { get { return this.context; } }
        public AccelBuilder Builder { get; set; }
        public AccelTraverser Traverser { get; set; }



        public IEnumerable<GeometryInstance> CreateGeometry(OptixMaterialInfo defaultMat, OptixMaterialInfo[] materials, SceneBuilder sb)
        {
            //create buffer descriptions
            BufferDesc vDesc = new BufferDesc() { Width = (uint)sb.SceneGeo.Vertices.Length, Format = Format.Float3, Type = BufferType.Input };
            BufferDesc nDesc = new BufferDesc() { Width = (uint)sb.SceneGeo.Normals.Length, Format = Format.Float3, Type = BufferType.Input };
            BufferDesc tcDesc = new BufferDesc() { Width = (uint)sb.SceneGeo.TexCoords.Length, Format = Format.Float2, Type = BufferType.Input };

            // Create the buffers to hold our geometry data
            Buffer vBuffer = EngineContext.Instance.ResourceManager.CreateBuffer(Context, vDesc);
            Buffer nBuffer = EngineContext.Instance.ResourceManager.CreateBuffer(Context, nDesc);
            Buffer tcBuffer = EngineContext.Instance.ResourceManager.CreateBuffer(Context, tcDesc);


            vBuffer.SetData(sb.SceneGeo.Vertices);
            nBuffer.SetData(sb.SceneGeo.Normals);
            tcBuffer.SetData(sb.SceneGeo.TexCoords.ToUVArray());
            sb.VertexBuffer = vBuffer;
            sb.NormalBuffer = nBuffer;
            sb.TexCoordBuffer = tcBuffer;
            var triMats = new List<uint>();

            var mats = materials.Select(i => i.Name).ToArray();
            List<GeometryInstance> instances = new List<GeometryInstance>();

            //foreach (ObjGroup group in mGroups) {
            //    ObjGroup _group = group;
            //    triMats.AddRange(group.VIndices.Select(nIndex => (uint) mats.IndexOf(_group.mtrl)));
            //}

            //BufferDesc mtDesc = new BufferDesc() { Width = (uint)triMats.Count, Format = Format.UInt, Type = BufferType.Input };
            //Optix.Buffer mtBuffer = new Optix.Buffer(Context, ref mtDesc);
            //mtBuffer.SetData(triMats.ToArray());
            Console.WriteLine();
            int primitiveIndex = 0;
            //ObjGroup _group = group;
            //triMats.AddRange(group.VIndices.Select(nIndex => (uint)mats.IndexOf(_group.mtrl)));
            //empty group);)
            //if (group.VIndices.Count == 0 && group.NIndices.Count == 0 && group.TIndices.Count == 0)continue;

            //ValidateGroup(group);

            bool normalsUseVIndices = sb.SceneGeo.Normals.Length > 0;

            //if (normalsUseVIndices)System.Diagnostics.Debug.Assert(mNormals.Count == mVertices.Count);

            foreach (var group in sb.Meshes)
            {
                ;
                var viDesc = new BufferDesc() { Width = (uint)(group.TrianglesCount), Format = Format.Int3, Type = BufferType.Input };
                var niDesc = new BufferDesc() { Width = (uint)(group.TrianglesCount), Format = Format.Int3, Type = BufferType.Input };
                var tiDesc = new BufferDesc() { Width = (uint)(group.TrianglesCount), Format = Format.Int3, Type = BufferType.Input };
                var ofsDesc = new BufferDesc()
                {
                    Width = (uint)(group.TrianglesCount),
                    Format = Format.UInt,
                    Type = BufferType.Input
                };

                var viBuffer = EngineContext.Instance.ResourceManager.CreateBuffer(Context, viDesc);
                var niBuffer = EngineContext.Instance.ResourceManager.CreateBuffer(Context, niDesc);
                var tiBuffer = EngineContext.Instance.ResourceManager.CreateBuffer(Context, tiDesc);

                var gIndexes =
                    sb.Triangles.ToList().GetRange(group.StartTriangle, group.TrianglesCount)
                         .SelectMany(tri => new[]
                             {
                                 tri.v0.VertexIndex,
                                 tri.v1.VertexIndex,
                                 tri.v2.VertexIndex
                             })
                         .ToArray();

                var gNIndexes =
                    sb.Triangles.ToList().GetRange(group.StartTriangle, group.TrianglesCount)
                         .SelectMany(tri => new[]
                             {
                                 tri.v0.NormalIndex,
                                 tri.v1.NormalIndex,
                                 tri.v2.NormalIndex
                             })
                         .ToArray();
                var gTIndexes =
                    sb.Triangles.ToList().GetRange(group.StartTriangle, group.TrianglesCount)
                         .SelectMany(tri => new[]
                             {
                                 tri.v0.TexCoordIndex,
                                 tri.v1.TexCoordIndex,
                                 tri.v2.TexCoordIndex
                             })
                         .ToArray();

                viBuffer.SetData(Convert(gIndexes).ToArray());
                niBuffer.SetData(Convert(gNIndexes).ToArray());
                tiBuffer.SetData(Convert(gTIndexes).ToArray());

                var geometry = new Geometry(Context);
                geometry.IntersectionProgram = new Program(Context, sb.IntersectionKernel.KernelPath, sb.IntersectionKernel.ProgramName);
                geometry.BoundingBoxProgram = new Program(Context, sb.BoundingBoxKernel.KernelPath, sb.BoundingBoxKernel.ProgramName);
                geometry.PrimitiveCount = (uint)(group.EndTriangle - group.StartTriangle);

                geometry["vertex_buffer"].Set(vBuffer);
                geometry["normal_buffer"].Set(nBuffer);
                geometry["texcoord_buffer"].Set(tcBuffer);
                geometry["vindex_buffer"].Set(viBuffer);
                geometry["nindex_buffer"].Set(niBuffer);
                geometry["tindex_buffer"].Set(tiBuffer);
                geometry["mesh_offset"].SetInt4(group.StartTriangle);

                var instance = new GeometryInstance(Context);
                instance.Geometry = geometry;
                instances.Add(instance);

                BaseTriangleMeshInfo groupCopy = group;
                OptixMaterialInfo mat = null;
                if (group.MeshName.ToLower().Contains("lt"))
                {
                    Tracer.TraceLine("Init mesh light {0}", group.MeshName);
                    mat = EngineContext.Instance.MaterialFactory.CreateMaterial(materials.First().Info,
                        BrdfClass.Experimental);
                    mat.Node["emission_color"].SetFloat3(new Vector3(1f, 1f, 1f));
                }
                else
                {
                    mat =
                        materials.FirstOrDefault(
                            m => m.Name.Equals(groupCopy.MaterialName, StringComparison.InvariantCultureIgnoreCase));
                }
                if (mat == null)
                    mat = defaultMat;
                instance.AddMaterial(mat.Node);
            }
            return instances;
        }

        private IEnumerable<Int3> Convert(int[] list)
        {
            for (int i = 0; i < list.Length; i += 3)
            {
                yield return new Int3(list[i], list[i + 1], list[i + 2]);
            }
        }
    }
}
